// util/base64.cpp


/*    Copyright 2009 10gen Inc.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

#include "tbcore/util/base64.hpp"

#include <sstream>

#include <boost/scoped_array.hpp>

#include "tbcore/base/assert.hpp"

TB_NAMESPACE_BEGIN

namespace base64 {

using std::string;
using std::stringstream;

class Alphabet {
public:
  Alphabet()
    : encode((unsigned char*)
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    "abcdefghijklmnopqrstuvwxyz"
    "0123456789"
    "+/"),
    decode(new unsigned char[257]) {
      memset( decode.get() , 0 , 256 );
      for ( int i=0; i<64; i++ ) {
        decode[ encode[i] ] = i;
      }

      test();
  }

  void test();

  char e( int x ) {
    return encode[x&0x3f];
  }

private:
  const unsigned char * encode;
public:
  boost::scoped_array<unsigned char> decode;
};

Alphabet alphabet;

void Alphabet::test() {
  TB_VERIFY( strlen( (char*)encode ) == 64 );
  for ( int i=0; i<26; i++ )
		TB_VERIFY(encode[i] == toupper(encode[i + 26]));
}

void encode( stringstream& ss , const char * data , int size ) {
    for ( int i=0; i<size; i+=3 ) {
        int left = size - i;
        const unsigned char * start = (const unsigned char*)data + i;

        // byte 0
        ss << alphabet.e(start[0]>>2);

        // byte 1
        unsigned char temp = ( start[0] << 4 );
        if ( left == 1 ) {
            ss << alphabet.e(temp);
            break;
        }
        temp |= ( ( start[1] >> 4 ) & 0xF );
        ss << alphabet.e(temp);

        // byte 2
        temp = ( start[1] & 0xF ) << 2;
        if ( left == 2 ) {
            ss << alphabet.e(temp);
            break;
        }
        temp |= ( ( start[2] >> 6 ) & 0x3 );
        ss << alphabet.e(temp);

        // byte 3
        ss << alphabet.e(start[2] & 0x3f);
    }

    int mod = size % 3;
    if ( mod == 1 ) {
        ss << "==";
    }
    else if ( mod == 2 ) {
        ss << "=";
    }
}

string encode( const char * data , int size ) {
    stringstream ss;
    encode( ss , data ,size );
    return ss.str();
}

string encode( const string& s ) {
    return encode( s.c_str() , (int)s.size() );
}

void decode( stringstream& ss , const string& s ) {
    TB_SMART_ASSERT( s.size() % 4 == 0 &&  "invalid base64")(s.size());
    const unsigned char * data = (const unsigned char*)s.c_str();
    int size = (int)s.size();

    unsigned char buf[3];
    for ( int i=0; i<size; i+=4) {
        const unsigned char * start = data + i;
        buf[0] = ( ( alphabet.decode[start[0]] << 2 ) & 0xFC ) | ( ( alphabet.decode[start[1]] >> 4 ) & 0x3 );
        buf[1] = ( ( alphabet.decode[start[1]] << 4 ) & 0xF0 ) | ( ( alphabet.decode[start[2]] >> 2 ) & 0xF );
        buf[2] = ( ( alphabet.decode[start[2]] << 6 ) & 0xC0 ) | ( ( alphabet.decode[start[3]] & 0x3F ) );

        int len = 3;
        if ( start[3] == '=' ) {
            len = 2;
            if ( start[2] == '=' ) {
                len = 1;
            }
        }
        ss.write( (const char*)buf , len );
    }
}

string decode( const string& s ) {
    stringstream ss;
    decode( ss , s );
    return ss.str();
}

const char* chars =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    "abcdefghijklmnopqrstuvwxyz"
    "0123456789+/=";

} //namespace base64

TB_NAMESPACE_END

